package sf.database.dialect;

import sf.database.dbinfo.ConnectInfo;
import sf.database.dbinfo.DBMetaData;
import sf.database.dbinfo.Feature;
import sf.database.meta.ColumnMapping;

import java.sql.SQLException;
import java.util.Date;

public interface DatabaseDialect {
    /**
     * 得到RDBMS的名称
     * @return
     */
    String getName();

    /**
     * 得到该数据库上该种数据类型的真实实现类型。
     * 比如，在不支持boolean类型的数据库上，会以char类型代替boolean；在不支持blob的数据库上，会以varbinary类型代替blob
     * @param sqlType
     * @return
     */
    int getImplementationSqlType(int sqlType);

    /**
     * 将表达式或值转换为文本形式的缺省值描述
     * @param defaultValue
     * @param sqlType
     * @return
     */
    String toDefaultString(Object defaultValue, int sqlType);

    /**
     * 判断数据库是否不支持某项特性
     * @param feature
     * @return
     */
    boolean notHas(Feature feature);

    /**
     * 判断数据库是否支持某项特性
     * @param feature
     * @return
     */
    boolean has(Feature feature);

    /**
     * 像Oracle，其Catlog是不用的，那么返回null mySQL没有Schema，每个database是一个catlog，那么返回值
     * 同时修正返回的大小写
     * @param schema
     * @return
     */
    String getCatlog(String schema);

    /**
     * 对于表名前缀的XX. MYSQL是作为catlog的，不是作为schema的 同时修正返回的大小写
     * @param schema
     * @return
     */
    String getSchema(String schema);

    /**
     * 获取数据库的默认驱动类
     * @param url Derby根据连接方式的不同，会有两种不同的DriverClass，因此需要传入url
     * @return 驱动类
     */
    String getDriverClass(String url);

    /**
     * 生成数据库连接字串
     * @param host     <=0则会使用默认端口
     * @param port
     * @param pathOrName
     * @return
     */
    String generateUrl(String host, int port, String pathOrName);

    /**
     * Oracle会将所有未加引号的数据库对象名称都按照大写对象名来处理，MySQL则对表名一律转小写，列名则保留原来的大小写。
     * 为了体现这一数据库策略的不同，这里处理大小写的问题。
     * <p>
     * 目前的原则是：凡是涉及
     * schema/table/view/sequence/dbname等转换的，都是用此方法，凡是设计列名转换，列别名定义的都用
     * @param name
     * @return
     */
    String getObjectNameToUse(String name);

    /**
     * 获得大小写正确的列名
     * @param name
     * @return
     */
    String getColumnNameToUse(ColumnMapping name);

    /**
     * 检查数据库是否包含指定的关键字，用来进行检查的对象名称都是按照getColumnNameIncase转换后的，因此对于大小写统一的数据库，
     * 这里无需考虑传入的大小写问题。
     * @param name
     * @return
     */
    boolean containKeyword(String name);

    /**
     * 当使用Timestamp类型的绑定变量操作时，转换为什么值
     * <h3>这个功能是干什么用的</h3>
     * 将java.util.Date转换为java.sql.Timestamp。这个功能本来和数据库方言是无关的，只涉及时间的转换。
     * 然而问题在于Oracle中，曾经只有Date类型。而且这个Date是年月日时分秒，但不到毫秒。
     * 也就是说，如果你使用Oracle来存储date（到秒），而当你将Date字段作为查询条件时，尴尬就发生了——
     * 传入java.sql.Date，会丢失时分秒精度；而传入java.sql.Timestamp作查询条件，有可能是查不到记录的，因为还有毫秒精度，还是查不到数据。
     * 因此本方言会将映射到Oracle时的精度缩减到秒。从而解决上述问题。
     * <p>
     * @param timestamp java.util.Date
     * @return java.sql.Timestamp
     */
    java.sql.Timestamp toTimestampSqlParam(Date timestamp);

    /**
     * 当出现异常时，使用此方法检查这个异常是否因为网络连接异常引起的。
     * <p>
     * <h3>用途</h3>
     * 用于内置连接池的刷新——可见自己实现一个连接池其实多么复杂。
     * 因为当数据库断开或重启时，往往所有的连接都会失效。此时使用连接池中的连接就会出错。
     * 比较典型的场景是数据库重启了一次，此时连接池里的连接看上去都是好好的，但实际上真正在上面执行SQL全都会出错。
     * <p>
     * 一种办法是当从连接池获得每个连接都对连接进行检查，这种检查必需是通过网络通信的检查，因为此时如果调用 Connection.isClosed()会正常返回false,
     * 因此这种检查就显得额外占开销，在高并发的OLTP中并不经济。
     * 因此试图实现一种更为有效的检查方式，即当任何数据库操作出现错误，并且这个错误是由连接IO引起，那么都要对连接池立刻进行清洗和检查，用这种触发机制来保证连接池的高效。
     * <p>
     * <h3>未来</h3>
     * 上述作为一种面向电信项目过程中设计方面的尝试，目前看来我更希望降低本框架的复杂度，让连接池独立出来，去除和连接池的耦合。并且连接池封装了所有的数据库操作，也能自行完成这一逻辑。
     * 所以未来版本中可能会去除，连接池应该由更专业的框架去完成。
     * @param se 异常
     * @return 如果是IO异常，那么返回true
     */
    boolean isIOError(SQLException se);

    /**
     * 根据 JDBC的URL，解析出其中的dbname,host，user等信息。目的是为了在不必连接数据库的情况下，得到数据库名称
     * @param connectInfo
     */
    public void parseDbInfo(ConnectInfo connectInfo);

    /**
     * 返回指定的属性(文本)
     * @param key 特性名称
     * @return 特性文本
     */
    public String getProperty(DBProperty key);

    /**
     * 返回指定的属性(文本)
     * @param key          特性名称
     * @param defaultValue 缺省值
     * @return 特性文本
     */
    public String getProperty(DBProperty key, String defaultValue);

    /**
     * 返回指定特性（数值），如果无值返回0
     * @param key 特性名称
     * @return 数值
     */
    public int getPropertyInt(DBProperty key);

    /**
     * 返回指定数据库特性(数值)，如果无值返回0
     * @param key 特性名称
     * @return 数值
     */
    public long getPropertyLong(DBProperty key);

    /**
     * 不同数据库登录后，所在的默认schema是不一样的
     * <ul>
     * <li>Oracle是以登录的用户名作为schema的。</li>
     * <li>mysql是只有catlog不区分schema的。</li>
     * <li>derby支持匿名访问，此时好像是位于APP这个schema下。</li>
     * <li>SQL Server默认是在dbo这个schema下</li>
     * </ul>
     * <br>因此对于无法确定当前schema的场合，使用这里提供的schema名称作为当前schema
     * @return 当前RDBMS的缺省Schema
     */
    String getDefaultSchema();

    /**
     * 针对非绑定变量SQL，生成SQL语句所用的文本值。 Java -> SQL String
     */
    String getSqlDateExpression(Date value);

    /**
     * 针对非绑定变量SQL，生成SQL语句所用的文本值。 Java -> SQL String
     */
    String getSqlTimeExpression(Date value);

    /**
     * 针对非绑定变量SQL，生成SQL语句所用的文本值。 Java -> SQL String
     */
    String getSqlTimestampExpression(Date value);

    /**
     * 初始化方言，根据JDBC接口进一步嗅探出数据库版本和JDBC驱动信息，从而让方言更加适配数据库操作。
     * 当有一个数据库实例连接初次创建时调用. Dialect可以通过直接连接数据库判断版本、函数等，调整Dialect内部的一些配置和数据。
     * @param db
     */
    void accept(DBMetaData db);

    /**
     * 数据库对象是否为大小写敏感的
     * 一般来说对应引号中的表名列名都是大小写敏感的。此处仅指没有引号的情况下是否大小写敏感
     * @return
     */
    boolean isCaseSensitive();
}
